package com.ruoyi.common.utils.file;

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.parser.PdfImageObject;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.exception.file.FileNameLengthLimitExceededException;
import com.ruoyi.common.exception.file.FileSizeLimitExceededException;
import com.ruoyi.common.exception.file.InvalidExtensionException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.common.utils.uuid.UUID;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.io.FilenameUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 文件上传工具类
 *
 * @author ruoyi
 */
public class FileUploadUtils {
    /**
     * 默认大小 50M
     */
    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;

    /**
     * 默认的文件名最大长度 100
     */
    public static final int DEFAULT_FILE_NAME_LENGTH = 100;

    /**
     * 默认上传的地址
     */
    private static String defaultBaseDir = RuoYiConfig.getProfile();

    public static String getDefaultBaseDir() {
        return defaultBaseDir;
    }

    public static void setDefaultBaseDir(String defaultBaseDir) {
        FileUploadUtils.defaultBaseDir = defaultBaseDir;
    }

    /**
     * 以默认配置进行文件上传
     *
     * @param file 上传的文件
     * @return 文件名称
     * @throws Exception
     */
    public static final String upload(MultipartFile file) throws IOException {
        try {
            return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
        } catch (Exception e) {
            throw new IOException(e.getMessage(), e);
        }
    }

    /**
     * 根据文件路径上传
     *
     * @param baseDir 相对应用的基目录
     * @param file    上传的文件
     * @return 文件名称
     * @throws IOException
     */
    public static final String upload(String baseDir, MultipartFile file) throws IOException {
        try {
            return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
        } catch (Exception e) {
            throw new IOException(e.getMessage(), e);
        }
    }

    /**
     * 文件上传
     *
     * @param baseDir          相对应用的基目录
     * @param file             上传的文件
     * @param allowedExtension 上传文件类型
     * @return 返回上传成功的文件名
     * @throws FileSizeLimitExceededException       如果超出最大大小
     * @throws FileNameLengthLimitExceededException 文件名太长
     * @throws IOException                          比如读写文件出错时
     * @throws InvalidExtensionException            文件校验异常
     */
    public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
            InvalidExtensionException {
        int fileNamelength = file.getOriginalFilename().length();
        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) {
            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
        }

        assertAllowed(file, allowedExtension);

        String fileName = extractFilename(file);

        boolean uploadImageCompress = RuoYiConfig.isUploadImageCompressEnabled();
        boolean uploadPdfCompress = RuoYiConfig.isUploadPdfCompressEnabled();
        if (uploadImageCompress && file.getContentType() != null && file.getContentType().startsWith("image")) {
            // todo 临时重复存储一份
            File tmpDesc = getAbsoluteFile(baseDir, fileName + "_bak");
            file.transferTo(tmpDesc);

            fileName = fileName.substring(0, fileName.lastIndexOf(".")) + ".jpg";

            File desc = getAbsoluteFile(baseDir, fileName);
            double uploadImageCompressByteLimit = RuoYiConfig.getUploadImageCompressByteLimit();
            FileUploadUtils.compressImage(file, desc, CompressType.BYTE_LIMIT, uploadImageCompressByteLimit);
        } else if (uploadPdfCompress && file.getContentType() != null && file.getContentType().contains("pdf")) {
            // todo 临时重复存储一份
            File tmpDesc = getAbsoluteFile(baseDir, fileName + "_bak");
            file.transferTo(tmpDesc);

            File desc = getAbsoluteFile(baseDir, fileName);
            double uploadPdfCompressLimit = RuoYiConfig.getUploadPdfCompressLimit();
            FileUploadUtils.compressPdf(file, desc.getAbsolutePath(), uploadPdfCompressLimit);
        } else {
            File desc = getAbsoluteFile(baseDir, fileName);
            file.transferTo(desc);
        }

        return getPathFileName(baseDir, fileName);
    }

    /**
     * 编码文件名
     */
    public static final String extractFilename(MultipartFile file) {
        String fileName = file.getOriginalFilename();
        String extension = getExtension(file);
        fileName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension;
        return fileName;
    }

    private static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException {
        File desc = new File(uploadDir + File.separator + fileName);

        if (!desc.getParentFile().exists()) {
            desc.getParentFile().mkdirs();
        }
        if (!desc.exists()) {
            desc.createNewFile();
        }
        return desc;
    }

    private static final String getPathFileName(String uploadDir, String fileName) throws IOException {
        int dirLastIndex = RuoYiConfig.getProfile().length() + 1;
        String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
        String pathFileName = Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
        return pathFileName;
    }

    /**
     * 文件大小校验
     *
     * @param file 上传的文件
     * @return
     * @throws FileSizeLimitExceededException 如果超出最大大小
     * @throws InvalidExtensionException
     */
    public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
            throws FileSizeLimitExceededException, InvalidExtensionException {
        long size = file.getSize();
        if (DEFAULT_MAX_SIZE != -1 && size > DEFAULT_MAX_SIZE) {
            throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
        }

        String fileName = file.getOriginalFilename();
        String extension = getExtension(file);
        if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) {
            if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) {
                throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
                        fileName);
            } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) {
                throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
                        fileName);
            } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) {
                throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
                        fileName);
            } else {
                throw new InvalidExtensionException(allowedExtension, extension, fileName);
            }
        }

    }

    /**
     * 判断MIME类型是否是允许的MIME类型
     *
     * @param extension
     * @param allowedExtension
     * @return
     */
    public static final boolean isAllowedExtension(String extension, String[] allowedExtension) {
        for (String str : allowedExtension) {
            if (str.equalsIgnoreCase(extension)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取文件名的后缀
     *
     * @param file 表单文件
     * @return 后缀名
     */
    public static final String getExtension(MultipartFile file) {
        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
        if (StringUtils.isEmpty(extension)) {
            extension = MimeTypeUtils.getExtension(file.getContentType());
        }
        return extension;
    }

    /**
     * 压缩图片
     *
     * @param multipartFile 上传文件
     * @param dest          压缩后文件
     * @param compressType  压缩判断类型
     * @param limit         压缩判断对应的限制
     */
    public static final void compressImage(MultipartFile multipartFile, File dest, CompressType compressType, double limit) {
        File tmpCompressDictionaryFile = new File(RuoYiConfig.getProfile() + "/tmp/compress");
        if (!tmpCompressDictionaryFile.exists()) {
            tmpCompressDictionaryFile.mkdirs();
        }

        //压缩图片，默认改为jpg
        File tmpCompressFile = new File(tmpCompressDictionaryFile + "/" + UUID.randomUUID() + ".jpg");
        try {
            Thumbnails.of(multipartFile.getInputStream()).scale(1).outputFormat("jpg").toFile(tmpCompressFile);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //根据不同判断条件，压缩
        switch (compressType) {
            case BYTE_LIMIT:
                if (tmpCompressFile.length() > limit) {
                    double scale = limit / tmpCompressFile.length();
                    try {
                        Thumbnails.of(tmpCompressFile).scale(scale).toFile(dest);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        tmpCompressFile.deleteOnExit();
                    }
                } else {
                    try {
                        Thumbnails.of(tmpCompressFile).scale(1).toFile(dest);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case SIZE_LIMIT:
                break;
            default:
                break;
        }
    }

    /**
     * 压缩图片
     *
     * @param file         上传文件
     * @param dest         压缩后文件
     * @param compressType 压缩判断类型
     * @param limit        压缩判断对应的限制
     */
    @Deprecated
    public static final void compressImage(File file, File dest, CompressType compressType, double limit) {
        File tmpCompressDictionaryFile = new File("d:/tmp/compress");
        if (!tmpCompressDictionaryFile.exists()) {
            tmpCompressDictionaryFile.mkdirs();
        }

        //压缩图片，默认改为jpg
        File tmpCompressFile = new File(tmpCompressDictionaryFile + "/" + UUID.randomUUID() + ".jpg");
        try {
            Thumbnails.of(file).scale(1).outputFormat("jpg").toFile(tmpCompressFile);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //根据不同判断条件，压缩
        switch (compressType) {
            case BYTE_LIMIT:
                if (tmpCompressFile.length() > limit) {
                    double scale = limit / tmpCompressFile.length();
                    try {
                        Thumbnails.of(tmpCompressFile).scale(scale).toFile(dest);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
//                        new File(tmpCompressPath).deleteOnExit();
                    }
                } else {
                    try {
                        Thumbnails.of(tmpCompressFile).scale(1).toFile(dest);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case SIZE_LIMIT:
                break;
            default:
                break;
        }
    }

    /**
     * 压缩pdf
     *
     * @param src   源文件，上传文件
     * @param dest  目标文件
     * @param limit 限制大小，单位为B
     */
    public static void compressPdf(MultipartFile src, String dest, double limit) {
        //小于限制，不压缩
        if (src.getSize() < limit) {
            try {
                src.transferTo(new File(dest));
            } catch (IOException e) {
                e.printStackTrace();
            }
            return;
        }

        //获取缩放比例
        double factor = RuoYiConfig.getUploadPdfCompressFactor();

        PdfName key = new PdfName("ITXT_SpecialId");
        PdfName value = new PdfName(String.valueOf(System.currentTimeMillis()));
        // 读取pdf文件
        PdfReader reader = null;
        try {
            reader = new PdfReader(src.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
        int n = reader.getXrefSize();
        PdfObject object;
        PRStream stream;
        // Look for image and manipulate image stream
        for (int i = 0; i < n; i++) {
            object = reader.getPdfObject(i);
//            PdfObject pdfObject = reader.getPdfObject(i);
            if (object == null || !object.isStream())
                continue;
            stream = (PRStream) object;
            PdfObject pdfsubtype = stream.get(PdfName.SUBTYPE);
            if (pdfsubtype != null && pdfsubtype.toString().equals(PdfName.IMAGE.toString())) {

                BufferedImage bi = null;
                try {
                    PdfImageObject image = new PdfImageObject(stream);
                    bi = image.getBufferedImage();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (bi == null) continue;
                int width = (int) (bi.getWidth() * factor);
                int height = (int) (bi.getHeight() * factor);
                BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                AffineTransform at = AffineTransform.getScaleInstance(factor, factor);
                Graphics2D g = img.createGraphics();
                g.drawRenderedImage(bi, at);
                ByteArrayOutputStream imgBytes = new ByteArrayOutputStream();
                try {
                    ImageIO.write(img, "JPG", imgBytes);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                stream.clear();
                stream.setData(imgBytes.toByteArray(), false, PRStream.BEST_COMPRESSION);
                stream.put(PdfName.TYPE, PdfName.XOBJECT);
                stream.put(PdfName.SUBTYPE, PdfName.IMAGE);
                stream.put(key, value);
                stream.put(PdfName.FILTER, PdfName.DCTDECODE);
                stream.put(PdfName.WIDTH, new PdfNumber(width));
                stream.put(PdfName.HEIGHT, new PdfNumber(height));
                stream.put(PdfName.BITSPERCOMPONENT, new PdfNumber(8));
                stream.put(PdfName.COLORSPACE, PdfName.DEVICERGB);
            }
        }
        // Save altered PDF
        PdfStamper stamper = null;
        try {
            stamper = new PdfStamper(reader, new FileOutputStream(dest));
            stamper.close();
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        reader.close();
    }

    /**
     * 压缩pdf
     *
     * @param src   源文件，普通文件
     * @param dest  目标文件
     * @param limit 限制大小，单位为B
     */
    @Deprecated
    public static void compressPdf(File src, String dest, double limit) throws IOException, DocumentException {
        //小于限制，不压缩
        if (src.length() < limit) {
            return;
        }

        //获取缩放比例
        double factor = RuoYiConfig.getUploadPdfCompressFactor();

        PdfName key = new PdfName("ITXT_SpecialId");
        PdfName value = new PdfName(String.valueOf(System.currentTimeMillis()));
        // 读取pdf文件
        PdfReader reader = new PdfReader(src.getAbsolutePath());
        int n = reader.getXrefSize();
        PdfObject object;
        PRStream stream;
        // Look for image and manipulate image stream
        for (int i = 0; i < n; i++) {
            object = reader.getPdfObject(i);
//            PdfObject pdfObject = reader.getPdfObject(i);
            if (object == null || !object.isStream())
                continue;
            stream = (PRStream) object;
            PdfObject pdfsubtype = stream.get(PdfName.SUBTYPE);
            if (pdfsubtype != null && pdfsubtype.toString().equals(PdfName.IMAGE.toString())) {
                PdfImageObject image = new PdfImageObject(stream);
                BufferedImage bi = image.getBufferedImage();
                if (bi == null) continue;
                int width = (int) (bi.getWidth() * factor);
                int height = (int) (bi.getHeight() * factor);
                BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                AffineTransform at = AffineTransform.getScaleInstance(factor, factor);
                Graphics2D g = img.createGraphics();
                g.drawRenderedImage(bi, at);
                ByteArrayOutputStream imgBytes = new ByteArrayOutputStream();
                ImageIO.write(img, "JPG", imgBytes);
                stream.clear();
                stream.setData(imgBytes.toByteArray(), false, PRStream.BEST_COMPRESSION);
                stream.put(PdfName.TYPE, PdfName.XOBJECT);
                stream.put(PdfName.SUBTYPE, PdfName.IMAGE);
                stream.put(key, value);
                stream.put(PdfName.FILTER, PdfName.DCTDECODE);
                stream.put(PdfName.WIDTH, new PdfNumber(width));
                stream.put(PdfName.HEIGHT, new PdfNumber(height));
                stream.put(PdfName.BITSPERCOMPONENT, new PdfNumber(8));
                stream.put(PdfName.COLORSPACE, PdfName.DEVICERGB);
            }
        }
        // Save altered PDF
        PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
        stamper.close();
        reader.close();
    }

    public static void main(String[] args) throws DocumentException, IOException {
//        FileUploadUtils.compressImage(new File("D:/a.png"), new File("D:/d.jpg"), CompressType.BYTE_LIMIT, 100000);
        FileUploadUtils.compressPdf(new File("D:\\共享\\compress\\a8469b47-7863-4042-a17c-dd6d62653f61.pdf"),
                "D:\\共享\\compress\\a.pdf", 1000000);

    }

    /**
     * 压缩限制类型
     */
    public enum CompressType {
        /**
         * 宽、高尺寸限制
         */
        SIZE_LIMIT,
        /**
         * 根据文件字节大小限制
         */
        BYTE_LIMIT
    }
}
